import {
    EditorSuggest,
    TFile,
    Editor,
    EditorPosition,
    EditorSuggestContext,
    EditorSuggestTriggerInfo,
    App, TFolder, Vault, HeadingCache, MarkdownView, Notice,
} from "obsidian";
import type HeadingSuggestPlugin from "src/main";
import {stripLeft} from "../utils";

interface IHeadingCompletion {
    label: string;
    location: string;
}

export class HeadingSuggestItem {
    headingCache: HeadingCache;
    file: TFile;

    constructor(headingCache: HeadingCache, file: TFile) {
        this.headingCache = headingCache;
        this.file = file;
    }

    toString(): string {
        return  "#".repeat(this.headingCache.level) + ` ${this.headingCache.heading}`;
    }
}

export default class HeadingSuggest extends EditorSuggest<IHeadingCompletion> {
    app: App;
    private plugin: HeadingSuggestPlugin;
    private upLevelHeadingSuggestions: Map<string, HeadingSuggestItem[]>;

    constructor(app: App, plugin: HeadingSuggestPlugin) {
        super(app);
        this.app = app;
        this.plugin = plugin;
    }

    getSuggestions(context: EditorSuggestContext): IHeadingCompletion[] | Promise<IHeadingCompletion[]> {
        const suggestions = this.getHeadingSuggestions(context);
        if (suggestions.length) {
            return suggestions;
        }

        // catch-all if there are no matches
        return [{ label: context.query, location: "nowhere"}];
    }

    getHeadingSuggestions(context: EditorSuggestContext): IHeadingCompletion[] {
        // const headings = this.getHeadings().map((item) => item.toString());
        const headings = this.getHeadings();
        const dedupedItems = Array.from(
            new Map(
                headings.map(item => [item.headingCache.heading, item])
            ).values()
        );

        // const dedupedHeadings = Array.from(new Set(headings));
        return dedupedItems.filter((item) => {
            return item.headingCache.heading.toLowerCase().contains(context.query.trim().toLowerCase());
        }).map((val) => ({ label: val.toString(), location: val.file.name }));
        // return dedupedHeadings.filter((item) => {
        //     // return stripLeft(item, "#").trim().toLowerCase().startsWith(context.query);
        //     return stripLeft(item, "#").trim().toLowerCase().contains(context.query.trim().toLowerCase());
        // }).map((val) => ({ label: val }));
    }

    renderSuggestion(suggestion: IHeadingCompletion, el: HTMLElement): void {
        el.setText(suggestion.label);
        el.setAttribute("title", suggestion.location);
    }

    selectSuggestion(suggestion: IHeadingCompletion, evt: MouseEvent | KeyboardEvent): void {
        const { editor } = this.context;
        const selectedHeadingText = suggestion.label;
        // const upLevelHeadingsText = this.upLevelHeadingSuggestions.get(selectedHeadingText);
        const reversedHeadingSuggestionItems = this.upLevelHeadingSuggestions.get(selectedHeadingText);
        const upLevelHeadingsText = reversedHeadingSuggestionItems.map((item) => {
            return "#".repeat(item.headingCache.level) + ` ${item.headingCache.heading}`;
        });
        const mdView = this.app.workspace.getActiveViewOfType(MarkdownView);
        const content = mdView.data;
        let replacementWithUpLevel = selectedHeadingText;
        for (const upLevelHeadingText of upLevelHeadingsText.reverse()) {
            if (content.indexOf(upLevelHeadingText) === -1) { // JM：表示现在文档中没有过这一层目录
                replacementWithUpLevel = upLevelHeadingText + "\n" + replacementWithUpLevel;
            }
        }
        editor.replaceRange(replacementWithUpLevel, this.context.start, this.context.end);
    }
    getUpLevelHeading(headingIndex: number, headingCandidates: HeadingCache[], headingChain: HeadingCache[]) {
        const currentHeading = headingCandidates[headingIndex];
        const level = currentHeading.level;
        if (level <= 2) {
            headingChain.push(currentHeading);
            return;
        }
        const upLevel = level - 1;
        for (let i = headingIndex; i>= 0; i--) {
            // 这里的headingCandicates是被遍历的markdown笔记中的所有heading
            // 这里发现如果不是rootHeading（这里2级heading，就是rootHeading了），则继续找他在本文件中的上一级heading，并加入headingChain中。
            if (headingCandidates[i].level <= upLevel){ // 找到上一级heading
                headingChain.push(currentHeading);
                this.getUpLevelHeading(i, headingCandidates, headingChain);
                break;
            }
        }
    }
    getUpLevelHeadingRecursively(cachedHeadings: HeadingCache[]) : Map<HeadingCache, HeadingCache[]>{
        const upLevelHeadingsArray = cachedHeadings.map((item, i) => {
            const chain: HeadingCache[] = [];
            this.getUpLevelHeading(i, cachedHeadings, chain);
            return chain;
        });

        const upLevelHeadingsMap = new Map();
        for(const heading of upLevelHeadingsArray){
            upLevelHeadingsMap.set(heading[0], heading.slice(1));
        }
        return upLevelHeadingsMap;
    }
    getHeadings(verifyFlag:boolean=false): HeadingSuggestItem[]{
        let headingSuggestions: HeadingSuggestItem[] = [];

        const markdownFiles: TFile[] = [];
        const startLocation = this.plugin.settings.vaultStartLocation.trim();
        const folderOrFile = this.app.vault.getAbstractFileByPath(startLocation);
        if (folderOrFile instanceof TFile) {
            markdownFiles.push(folderOrFile);
        } else if (folderOrFile instanceof TFolder) {
            Vault.recurseChildren(folderOrFile, (file => {
                if (file instanceof TFile && file.extension === "md") {
                    markdownFiles.push(file);
                }
            }));
        }

        // const markdownFiles = this.app.vault.getMarkdownFiles();
        markdownFiles.sort((a, b) => {
            if (a.stat.ctime > b.stat.ctime) return -1;
            if (a.stat.ctime < b.stat.ctime) return 1;
            return 0;
        });

        // const upLevelHeadingSuggestions: Map<string, string> = new Map();
        this.upLevelHeadingSuggestions = new Map();
        markdownFiles.forEach((markFile: TFile) => {
            for (const ignoredSuffix of this.plugin.settings.ignoredSuffixArray) {
                if (markFile.path.endsWith(ignoredSuffix)) {
                    return null;
                }
            }
            if (this.app.metadataCache.getFileCache(markFile) && this.app.metadataCache.getFileCache(markFile).headings) {
                const cachedHeadings = this.app.metadataCache.getFileCache(markFile).headings;
                const upLevelHeadingsMap = this.getUpLevelHeadingRecursively(cachedHeadings);
                upLevelHeadingsMap.forEach((headingCacheArray, keyHeading) => {
                    const reversedArray = headingCacheArray.reverse();
                    const reversedHeadingSuggestionItems = reversedArray.map((headingCache) => {
                        return new HeadingSuggestItem(headingCache, markFile);
                    });
                    // const upLevelHeadingsText: string[] = reversedArray.map(v => "#".repeat(v.level) + ` ${v.heading}`);
                    const mapKey = "#".repeat(keyHeading.level) + ` ${keyHeading.heading}`;
                    // JM：这里是新添加的verify功能：如果发现map中已有的级联和新添加的级联不一致，则告警，和本身的逻辑没有直接关系，也不会阻断。
                    if (verifyFlag && this.upLevelHeadingSuggestions.has(mapKey)) {
                        const oldValue = this.upLevelHeadingSuggestions.get(mapKey).map((v) => { return v.toString()}).join("/");
                        const newValue = reversedHeadingSuggestionItems.map((v) => { return v.toString()}).join("/");
                        let oldValueMarkdownFile = "";
                        if (this.upLevelHeadingSuggestions.get(mapKey).length > 0) {
                            oldValueMarkdownFile = this.upLevelHeadingSuggestions.get(mapKey)[0].file.name;
                        }
                        // const newValue = upLevelHeadingsText.join("/");
                        if (oldValue.toLowerCase().trim() !== newValue.toLowerCase().trim()){
                            // key已经存在，且发现冲突了，就不要去替换了，否则另一个又要继续报错
                            new Notice(`conflict: ${markFile.name}@${mapKey}, ${newValue} -> old: ${oldValue}@${oldValueMarkdownFile}`);
                            console.warn(`conflict: ${markFile.name}@${mapKey}, ${newValue} -> old: ${oldValue}@${oldValueMarkdownFile}`);
                        } else {
                            // key已经存在，且是和原值一致的，那就什么都不用干。
                        }
                    } else {
                        // key不存在，直接设置
                        this.upLevelHeadingSuggestions.set(mapKey, reversedHeadingSuggestionItems);
                    }
                });
                // console.log(upLevelHeadingsMap);
                if (cachedHeadings){
                    // 遍历每个markdown文件，将文件中的headings添加到总的headingSuggestions中去
                    headingSuggestions = headingSuggestions.concat(cachedHeadings.map((v) => {
                        return new HeadingSuggestItem(v, markFile);
                    }));
                    // headingSuggestions = headingSuggestions.concat(cachedHeadings.map((v) => {
                    //     // return  "#".repeat(v.level) + ` ${v.heading} (${markFile.path})`;
                    //     return  "#".repeat(v.level) + ` ${v.heading}`; // TODO: 最好加一个hint显示在哪个file中，但是去重要手动做
                    // }));
                }
            }
        });
        // headingSuggestions = headingSuggestions.filter(heading => { return !this.plugin.settings.ingoredHeaders.contains(heading); });
        headingSuggestions = headingSuggestions.filter(heading => { return !this.plugin.settings.ingoredHeaders.contains(heading.toString()); });
        return headingSuggestions;
        // return Array.from(new Set(headingSuggestions));
    }

    onTrigger(
        cursor: EditorPosition,
        editor: Editor,
        file: TFile
    ): EditorSuggestTriggerInfo {
        if (!this.plugin.settings.isAutosuggestEnabled) {
            return null;
        }

        const triggerPhrase = this.plugin.settings.autocompleteTriggerPhrase;
        const startPos = this.context?.start || {
            line: cursor.line,
            ch: cursor.ch - triggerPhrase.length,
        };

        if (!editor.getRange(startPos, cursor).startsWith(triggerPhrase)) {
            return null;
        }

        const precedingChar = editor.getRange(
            {
                line: startPos.line,
                ch: startPos.ch - 1,
            },
            startPos
        );

        // Short-circuit if `@` as a part of a word (e.g. part of an email address)
        // if (precedingChar && /[`a-zA-Z0-9]/.test(precedingChar)) {
        //     return null;
        // }

        // this.getHeadings();

        return {
            start: startPos,
            end: cursor,
            query: editor.getRange(startPos, cursor).substring(triggerPhrase.length),
        };
    }
}